﻿using System.Collections.Generic;
using Microsoft.SharePoint;
using System;

namespace SKN
{
    /// <summary>
    /// Contains common method for ContentType
    /// </summary>
    public class ContentType
    {
        /// <summary>
        /// This method will ensure all the content type changes are propogated to list content types
        /// </summary>
        /// <param name="web"></param>
        /// <param name="contentTypeName"></param>
        /// <remarks>This piece of code is copied from http://blog.beckybertram.com/Lists/Posts/Post.aspx?List=eb3e1762-bab0-4e96-8bc5-d67d6e6bfd44&ID=18</remarks>
        public static void RefreshListContentType (SPSite site, string contentTypeName)
        {
            using (SPWeb rootWeb = site.RootWeb)
            {
                //Get the source site content type
                SPContentType sourceCT = rootWeb.AvailableContentTypes[contentTypeName];
                if (sourceCT == null)
                {
                    throw new ArgumentException("Unable to find Content Type named \"" + contentTypeName + "\"");
                }

                IList<SPContentTypeUsage> ctUsageList = SPContentTypeUsage.GetUsages(sourceCT);
                foreach (SPContentTypeUsage ctu in ctUsageList)
                {
                    if (!ctu.IsUrlToList)
                        continue;

                    using (SPWeb web = site.OpenWeb(ctu.Url))
                    {
                        SPList list = web.GetList(ctu.Url);
                        SPContentType listCT = list.ContentTypes[ctu.Id];
                        ProcessContentType(list, sourceCT, listCT, true, true);
                    }
                }
            }
        }


        private static void ProcessContentType (SPList list, SPContentType sourceCT, SPContentType listCT, bool updateFields, bool removeFields)
        {
            if (listCT == null)
                return;

            if (updateFields)
            {
                UpdateListFields(list, listCT, sourceCT);
            }

            //Find/add the fields to add
            foreach (SPFieldLink sourceFieldLink in sourceCT.FieldLinks)
            {
                if (FieldExist(sourceCT, sourceFieldLink))
                {
                    if (!FieldExist(listCT, sourceFieldLink))
                    {
                        //Perform double update, just to be safe
                        // (but slow)

                        if (listCT.FieldLinks[sourceFieldLink.Id] != null)
                        {
                            listCT.FieldLinks.Delete(sourceFieldLink.Id);
                            listCT.Update();
                        }
                        listCT.FieldLinks.Add(new SPFieldLink(sourceCT.Fields[sourceFieldLink.Id]));
                        listCT.Update();
                    }
                }
            }

            if (removeFields)
            {
                //Find the fields to delete
                //WARNING this part of the code has not been
                // adequately tested (though what could go wrong? 

                //Copy collection to avoid modifying enumeration as we go through it
                List<SPFieldLink> listFieldLinks = new List<SPFieldLink>();
                foreach (SPFieldLink listFieldLink in listCT.FieldLinks)
                {
                    listFieldLinks.Add(listFieldLink);
                }

                foreach (SPFieldLink listFieldLink in listFieldLinks)
                {
                    if (!FieldExist(sourceCT, listFieldLink))
                    {
                        listCT.FieldLinks.Delete(listFieldLink.Id);
                        listCT.Update();
                    }
                }
            }
        }

        /// <summary>
        /// Updates the fields of the list content type (listCT) with the
        /// fields found on the source content type (courceCT).
        /// </summary>
        /// <param name="list">The list.</param>
        /// <param name="listCT">The list CT.</param>
        /// <param name="sourceCT">The source CT.</param>
        private static void UpdateListFields (SPList list, SPContentType listCT, SPContentType sourceCT)
        {
            foreach (SPFieldLink sourceFieldLink in sourceCT.FieldLinks)
            {
                //has the field changed? If not, continue.
                if (listCT.FieldLinks[sourceFieldLink.Id] != null
                     && listCT.FieldLinks[sourceFieldLink.Id].SchemaXml
                        == sourceFieldLink.SchemaXml)
                {
                    continue;
                }
                if (!FieldExist(sourceCT, sourceFieldLink))
                {
                    continue;
                }

                if (listCT.FieldLinks[sourceFieldLink.Id] != null)
                {
                    listCT.FieldLinks.Delete(sourceFieldLink.Id);
                    listCT.Update();
                }

                listCT.FieldLinks.Add(new SPFieldLink(sourceCT.Fields[sourceFieldLink.Id]));

                //Set displayname, not set by previous operation
                listCT.FieldLinks[sourceFieldLink.Id].DisplayName = sourceCT.FieldLinks[sourceFieldLink.Id].DisplayName;
                listCT.Update();

            }
        }

        /// <summary>
        /// Fields the exist.
        /// </summary>
        /// <param name="contentType">Type of the content.</param>
        /// <param name="fieldLink">The field link.</param>
        /// <returns></returns>
        public static bool FieldExist (SPContentType contentType, SPFieldLink fieldLink)
        {
            try
            {
                //will throw exception on missing fields
                return contentType.Fields[fieldLink.Id] != null;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Use this method to quickly Swap content type for all list items in a list / library
        /// </summary>
        /// <param name="web"></param>
        /// <param name="listName">List / Library UI name</param>
        /// <param name="oldContentTypeName">Pass the UI name of the old content type </param>
        /// <param name="newContentTypeName">Pass the UI name of the new content type </param>
        /// <example>ContentTypes.SwapContentType(web, "Pages", "Article Page", "Welcome Page");</example>
        public static void SwapContentType (SPWeb web, string listName, string oldContentTypeName, string newContentTypeName)
        {
            SPContentType oldContentType = web.AvailableContentTypes[oldContentTypeName];
            SPContentType newContentType = web.AvailableContentTypes[newContentTypeName];

            SwapContentType(web, listName, oldContentType, newContentType);
        }

        /// <summary>
        /// Use this method to quickly Swap content type for all list items in a list / library
        /// </summary>
        /// <param name="web"></param>
        /// <param name="listName">List / Library UI name</param>
        /// <param name="oldContentType">Pass the content type object of old content type</param>
        /// <param name="newContentType">Pass the content type object of new content type</param>
        public static void SwapContentType (SPWeb web, string listName, SPContentType oldContentType, SPContentType newContentType)
        {
            SPList list = web.Lists[listName];
            string contentTypeInternalType = "ContentType";
            string contentTypeInternalTypeId = "ContentTypeId";
            string checkInComment = "Content Type updated programatically.";

            // First we will gather the item id's of items matching the old content type name 
            List<int> itemIDs = new List<int>();
            foreach (SPListItem listItem in list.Items)
            {
                if (string.Equals(listItem[contentTypeInternalType], oldContentType.Name))
                {
                    itemIDs.Add(listItem.ID);
                }
            }

            // Identified items in the list will be processed 
            foreach (int itemID in itemIDs)
            {
                SPListItem listItem = list.GetItemById(itemID);
                SPFile file = listItem.File;
                file.CheckOut();
                listItem[contentTypeInternalTypeId] = newContentType.Id;
                // This is not required as once we update Id , name will automatically get changed.
                //listItem[contentTypeInternalType] = newContentType.Name;

                listItem.Update();

                file.CheckIn(checkInComment);

                file.Publish(checkInComment);

                if (list.EnableModeration)
                {
                    if (listItem.ModerationInformation.Status.Equals(SPModerationStatusType.Pending))
                    {
                        file.Approve(checkInComment);
                    }
                }
            }
        }
    }
}